React özel hook etki temizliğinin sırlarını keşfedin. Bellek sızıntılarını önlemeyi, kaynakları yönetmeyi ve küresel kitleler için yüksek performanslı, kararlı React uygulamaları oluşturmayı öğrenin.
React Özel Hook Etki Temizliği: Sağlam Uygulamalar İçin Yaşam Döngüsü Yönetiminde Uzmanlaşma
Modern web geliştirmenin geniş ve birbirine bağlı dünyasında, React, geliştiricilere dinamik ve etkileşimli kullanıcı arayüzleri oluşturma gücü veren baskın bir güç olarak ortaya çıkmıştır. React'in fonksiyonel component paradigmasının kalbinde, yan etkileri yönetmek için güçlü bir araç olan useEffect hook'u yer alır. Ancak, büyük güç büyük sorumluluk getirir ve bu etkileri doğru bir şekilde nasıl temizleyeceğini anlamak sadece bir en iyi pratik değil, aynı zamanda küresel bir kitleye hitap eden kararlı, performanslı ve güvenilir uygulamalar oluşturmak için temel bir gerekliliktir.
Bu kapsamlı kılavuz, React özel hook'ları içindeki kritik bir konu olan etki temizliğinin derinliklerine inecektir. Temizliğin neden vazgeçilmez olduğunu keşfedecek, yaşam döngüsü yönetimine titizlikle dikkat edilmesi gereken yaygın senaryoları inceleyecek ve bu temel beceride ustalaşmanıza yardımcı olacak pratik, küresel olarak uygulanabilir örnekler sunacağız. İster bir sosyal platform, ister bir e-ticaret sitesi veya analitik bir gösterge paneli geliştiriyor olun, burada tartışılan ilkeler uygulama sağlığını ve yanıt verebilirliğini korumak için evrensel olarak hayati öneme sahiptir.
React'in useEffect Hook'unu ve Yaşam Döngüsünü Anlamak
Temizlikte ustalaşma yolculuğuna çıkmadan önce, useEffect hook'unun temellerini kısaca gözden geçirelim. React Hook'ları ile tanıtılan useEffect, fonksiyonel component'lerin yan etkileri gerçekleştirmesine olanak tanır – yani tarayıcı, ağ veya diğer harici sistemlerle etkileşime geçmek için React component ağacının dışına çıkan eylemler. Bunlar arasında veri çekme, DOM'u manuel olarak değiştirme, abonelikler kurma veya zamanlayıcılar başlatma yer alabilir.
useEffect'in Temelleri: Etkiler Ne Zaman Çalışır
Varsayılan olarak, useEffect'e geçirilen fonksiyon, component'inizin her tamamlanmış render işleminden sonra çalışır. Bu durum, doğru yönetilmezse sorunlu olabilir, çünkü yan etkiler gereksiz yere çalışarak performans sorunlarına veya hatalı davranışlara yol açabilir. Etkilerin ne zaman yeniden çalışacağını kontrol etmek için useEffect ikinci bir argüman kabul eder: bir bağımlılık dizisi.
- Bağımlılık dizisi atlanırsa, etki her render'dan sonra çalışır.
- Boş bir dizi (
[]) sağlanırsa, etki yalnızca ilk render'dan sonra bir kez çalışır (componentDidMount'a benzer şekilde) ve temizleme fonksiyonu component kaldırıldığında bir kez çalışır (componentWillUnmount'a benzer şekilde). - Bağımlılıkları olan bir dizi (
[dep1, dep2]) sağlanırsa, etki yalnızca bu bağımlılıklardan herhangi biri render'lar arasında değiştiğinde yeniden çalışır.
Bu temel yapıyı düşünün:
Butona {count} kez tıkladınız
import React, { useEffect, useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// Bu etki, bağımlılık dizisi sağlanmazsa her render'dan sonra çalışır
// veya [count] bağımlılık olarak verilirse 'count' değiştiğinde çalışır.
document.title = `Sayı: ${count}`;
// Geri dönen fonksiyon temizleme mekanizmasıdır
return () => {
// Bu, etki yeniden çalışmadan önce (bağımlılıklar değişirse) çalışır
// ve component kaldırıldığında çalışır.
console.log('Sayı etkisi için temizleme');
};
}, [count]); // Bağımlılık dizisi: count değiştiğinde etki yeniden çalışır
return (
"Temizleme" Kısmı: Ne Zaman ve Neden Önemli
useEffect'in temizleme mekanizması, etki geri çağrısı (callback) tarafından döndürülen bir fonksiyondur. Bu fonksiyon çok önemlidir çünkü etki tarafından ayrılan kaynakların veya başlatılan işlemlerin artık ihtiyaç duyulmadığında düzgün bir şekilde geri alınmasını veya durdurulmasını sağlar. Temizleme fonksiyonu iki ana senaryoda çalışır:
- Etki yeniden çalışmadan önce: Etkinin bağımlılıkları varsa ve bu bağımlılıklar değişirse, önceki etki yürütmesinden gelen temizleme fonksiyonu, yeni etki yürütülmeden önce çalışır. Bu, yeni etki için temiz bir başlangıç sağlar.
- Component kaldırıldığında (unmount): Component DOM'dan kaldırıldığında, son etki yürütmesinden gelen temizleme fonksiyonu çalışır. Bu, bellek sızıntılarını ve diğer sorunları önlemek için esastır.
Bu temizlik küresel uygulama geliştirmesi için neden bu kadar kritik?
- Bellek Sızıntılarını Önleme: Abonelikten çıkılmamış olay dinleyicileri, temizlenmemiş zamanlayıcılar veya kapatılmamış ağ bağlantıları, onları oluşturan component kaldırıldıktan sonra bile bellekte kalabilir. Zamanla, bu unutulmuş kaynaklar birikerek performans düşüşüne, yavaşlamaya ve sonunda uygulamanın çökmesine yol açar – dünyanın herhangi bir yerindeki herhangi bir kullanıcı için sinir bozucu bir deneyim.
- Beklenmedik Davranışları ve Hataları Önleme: Uygun temizlik olmadan, eski bir etki, eski veriler üzerinde çalışmaya veya var olmayan bir DOM öğesiyle etkileşime girmeye devam edebilir, bu da çalışma zamanı hatalarına, yanlış UI güncellemelerine ve hatta güvenlik açıklarına neden olabilir. Artık görünür olmayan bir component için veri getirmeye devam eden bir aboneliğin gereksiz ağ isteklerine veya durum güncellemelerine neden olabileceğini düşünün.
- Performansı Optimize Etme: Kaynakları zamanında serbest bırakarak, uygulamanızın yalın ve verimli kalmasını sağlarsınız. Bu, özellikle dünyanın birçok yerinde yaygın bir senaryo olan daha az güçlü cihazlarda veya sınırlı ağ bant genişliğine sahip kullanıcılar için önemlidir.
- Veri Tutarlılığını Sağlama: Temizlik, öngörülebilir bir durumu korumaya yardımcı olur. Örneğin, bir component veri çeker ve ardından başka bir yere giderse, getirme işlemini temizlemek, component'in kaldırıldıktan sonra gelen bir yanıtı işlemeye çalışmasını önler, bu da hatalara yol açabilir.
Özel Hook'larda Etki Temizliği Gerektiren Yaygın Senaryolar
Özel hook'lar, durum bilgisi olan mantığı ve yan etkileri yeniden kullanılabilir fonksiyonlara soyutlamak için React'te güçlü bir özelliktir. Özel hook'lar tasarlarken, temizlik onların sağlamlığının ayrılmaz bir parçası haline gelir. Şimdi etki temizliğinin kesinlikle gerekli olduğu en yaygın senaryolardan bazılarını inceleyelim.
1. Abonelikler (WebSocket'ler, Olay Yayıcılar)
Birçok modern uygulama, gerçek zamanlı verilere veya iletişime dayanır. WebSocket'ler, sunucu tarafından gönderilen olaylar veya özel olay yayıcılar bunun başlıca örnekleridir. Bir component böyle bir akışa abone olduğunda, component'in artık veriye ihtiyacı kalmadığında abonelikten çıkmak hayati önem taşır, aksi takdirde abonelik aktif kalarak kaynakları tüketir ve potansiyel olarak hatalara neden olur.
Örnek: Bir useWebSocket Özel Hook'u
Bağlantı durumu: {isConnected ? 'Çevrimiçi' : 'Çevrimdışı'} Son Mesaj: {message}
import React, { useEffect, useState } from 'react';
function useWebSocket(url) {
const [message, setMessage] = useState(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('WebSocket bağlandı');
setIsConnected(true);
};
ws.onmessage = (event) => {
console.log('Mesaj alındı:', event.data);
setMessage(event.data);
};
ws.onclose = () => {
console.log('WebSocket bağlantısı kesildi');
setIsConnected(false);
};
ws.onerror = (error) => {
console.error('WebSocket hatası:', error);
setIsConnected(false);
};
// Temizleme fonksiyonu
return () => {
if (ws.readyState === WebSocket.OPEN) {
console.log('WebSocket bağlantısı kapatılıyor');
ws.close();
}
};
}, [url]); // URL değişirse yeniden bağlan
return { message, isConnected };
}
// Bir component'te kullanımı:
function RealTimeDataDisplay() {
const { message, isConnected } = useWebSocket('wss://echo.websocket.events');
return (
Gerçek Zamanlı Veri Durumu
Bu useWebSocket hook'unda, temizleme fonksiyonu, bu hook'u kullanan component'in kaldırılması durumunda (örneğin, kullanıcı farklı bir sayfaya geçtiğinde) WebSocket bağlantısının düzgün bir şekilde kapatılmasını sağlar. Bu olmadan, bağlantı açık kalır, ağ kaynaklarını tüketir ve potansiyel olarak artık UI'da var olmayan bir component'e mesaj göndermeye çalışır.
2. Olay Dinleyicileri (DOM, Global Nesneler)
Belgeye, pencereye veya belirli DOM öğelerine olay dinleyicileri eklemek yaygın bir yan etkidir. Ancak, bu dinleyicilerin bellek sızıntılarını önlemek ve işleyicilerin (handler) kaldırılmış component'ler üzerinde çağrılmamasını sağlamak için kaldırılması gerekir.
Örnek: Bir useClickOutside Özel Hook'u
Bu hook, referans verilen bir elemanın dışındaki tıklamaları algılar; bu, açılır menüler, modallar veya gezinme menüleri için kullanışlıdır.
Bu bir modal iletişim kutusudur.
import React, { useEffect } from 'react';
function useClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
// ref'in elemanına veya alt elemanlarına tıklanırsa hiçbir şey yapma
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
// Temizleme fonksiyonu: olay dinleyicilerini kaldır
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]); // Sadece ref veya handler değişirse yeniden çalıştır
}
// Bir component'te kullanımı:
function Modal() {
const modalRef = React.useRef();
const [isOpen, setIsOpen] = React.useState(true);
useClickOutside(modalRef, () => setIsOpen(false));
if (!isOpen) return null;
return (
Kapatmak için Dışarı Tıklayın
Buradaki temizlik hayati önem taşır. Eğer modal kapatılır ve component kaldırılırsa, mousedown ve touchstart dinleyicileri aksi takdirde document üzerinde kalıcı olur, bu da artık var olmayan ref.current'a erişmeye çalışırlarsa hatalara yol açabilir veya beklenmedik işleyici çağrılarına neden olabilir.
3. Zamanlayıcılar (setInterval, setTimeout)
Zamanlayıcılar animasyonlar, geri sayımlar veya periyodik veri güncellemeleri için sıkça kullanılır. Yönetilmeyen zamanlayıcılar, React uygulamalarında klasik bir bellek sızıntısı ve beklenmedik davranış kaynağıdır.
Örnek: Bir useInterval Özel Hook'u
Bu hook, temizliği otomatik olarak halleden bildirimsel (declarative) bir setInterval sağlar.
import React, { useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// En son callback'i hatırla.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// interval'ı kur.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
// Temizleme fonksiyonu: interval'ı temizle
return () => clearInterval(id);
}
}, [delay]);
}
// Bir component'te kullanımı:
function Counter() {
const [count, setCount] = React.useState(0);
useInterval(() => {
// Özel mantığınız buraya
setCount(count + 1);
}, 1000); // Her 1 saniyede bir güncelle
return Sayıcı: {count}
;
}
Burada, clearInterval(id) temizleme fonksiyonu çok önemlidir. Eğer Counter component'i interval'ı temizlemeden kaldırılırsa, `setInterval` geri çağrısı her saniye yürütülmeye devam eder, kaldırılmış bir component üzerinde setCount'u çağırmaya çalışır, bu da React'in uyaracağı ve bellek sorunlarına yol açabilecek bir durumdur.
4. Veri Çekme ve AbortController
Bir API isteğinin kendisi genellikle tamamlanmış bir eylemi 'geri alma' anlamında 'temizlik' gerektirmese de, devam eden bir istek gerektirebilir. Bir component bir veri çekme işlemi başlatır ve istek tamamlanmadan önce kaldırılırsa, promise hala çözümlenebilir veya reddedilebilir, bu da potansiyel olarak kaldırılmış bir component'in durumunu güncelleme girişimlerine yol açabilir. AbortController, bekleyen fetch isteklerini iptal etmek için bir mekanizma sağlar.
Örnek: AbortController ile Bir useDataFetch Özel Hook'u
Kullanıcı profili yükleniyor... Hata: {error.message} Kullanıcı verisi yok. İsim: {user.name} E-posta: {user.email}
import React, { useState, useEffect } from 'react';
function useDataFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP hatası! durum: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch iptal edildi');
} else {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
// Temizleme fonksiyonu: fetch isteğini iptal et
return () => {
abortController.abort();
console.log('Veri çekme, unmount/re-render sırasında iptal edildi');
};
}, [url]); // URL değişirse yeniden fetch et
return { data, loading, error };
}
// Bir component'te kullanımı:
function UserProfile({ userId }) {
const { data: user, loading, error } = useDataFetch(`https://api.example.com/users/${userId}`);
if (loading) return Kullanıcı Profili
Temizleme fonksiyonundaki abortController.abort() kritik öneme sahiptir. Eğer UserProfile bir fetch isteği devam ederken kaldırılırsa, bu temizlik isteği iptal edecektir. Bu, gereksiz ağ trafiğini önler ve daha da önemlisi, promise'in daha sonra çözümlenmesini ve potansiyel olarak kaldırılmış bir component üzerinde setData veya setError'u çağırmaya çalışmasını durdurur.
5. DOM Manipülasyonları ve Harici Kütüphaneler
Doğrudan DOM ile etkileşime girdiğinizde veya kendi DOM öğelerini yöneten üçüncü taraf kütüphaneleri (örneğin, grafik kütüphaneleri, harita bileşenleri) entegre ettiğinizde, genellikle kurulum ve söküm işlemleri yapmanız gerekir.
Örnek: Bir Grafik Kütüphanesini Başlatma ve Yok Etme (Kavramsal)
import React, { useEffect, useRef } from 'react';
// ChartLibrary'nin Chart.js veya D3 gibi harici bir kütüphane olduğunu varsayın
import ChartLibrary from 'chart-library';
function useChart(data, options) {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (chartRef.current) {
// mount sırasında grafik kütüphanesini başlat
chartInstance.current = new ChartLibrary(chartRef.current, { data, options });
}
// Temizleme fonksiyonu: grafik örneğini yok et
return () => {
if (chartInstance.current) {
chartInstance.current.destroy(); // Kütüphanenin bir destroy metodu olduğunu varsayar
chartInstance.current = null;
}
};
}, [data, options]); // Veri veya seçenekler değişirse yeniden başlat
return chartRef;
}
// Bir component'te kullanımı:
function SalesChart({ salesData }) {
const chartContainerRef = useChart(salesData, { type: 'bar' });
return (
Temizlemedeki chartInstance.current.destroy() esastır. Bu olmadan, grafik kütüphanesi DOM öğelerini, olay dinleyicilerini veya diğer dahili durumlarını geride bırakabilir, bu da bellek sızıntılarına ve aynı konumda başka bir grafik başlatılırsa veya component yeniden render edilirse potansiyel çakışmalara yol açabilir.
Temizlik ile Sağlam Özel Hook'lar Oluşturma
Özel hook'ların gücü, karmaşık mantığı kapsülleyerek yeniden kullanılabilir ve test edilebilir hale getirme yeteneklerinde yatar. Bu hook'lar içinde temizliği doğru bir şekilde yönetmek, bu kapsüllenmiş mantığın aynı zamanda sağlam ve yan etkiyle ilgili sorunlardan arınmış olmasını sağlar.
Felsefe: Kapsülleme ve Yeniden Kullanılabilirlik
Özel hook'lar 'Kendini Tekrar Etme' (DRY) ilkesini izlemenize olanak tanır. useEffect çağrılarını ve ilgili temizlik mantığını birden çok component'e dağıtmak yerine, bunu bir özel hook'ta merkezileştirebilirsiniz. Bu, kodunuzu daha temiz, anlaşılması daha kolay ve hatalara daha az eğilimli hale getirir. Bir özel hook kendi temizliğini yaptığında, o hook'u kullanan herhangi bir component otomatik olarak sorumlu kaynak yönetiminden faydalanır.
Şimdi önceki örneklerden bazılarını geliştirip genişleterek, küresel uygulama ve en iyi pratikleri vurgulayalım.
Örnek 1: useWindowSize – Küresel Olarak Duyarlı Bir Olay Dinleyici Hook'u
Duyarlı tasarım, çeşitli ekran boyutlarına ve cihazlara uyum sağlayarak küresel bir kitle için anahtardır. Bu hook, pencere boyutlarını izlemeye yardımcı olur.
Pencere Genişliği: {width}px Pencere Yüksekliği: {height}px
Ekranınız şu anda {width < 768 ? 'küçük' : 'büyük'}.
Bu uyarlanabilirlik, dünya genelindeki çeşitli cihazlardaki kullanıcılar için çok önemlidir.
import React, { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
});
useEffect(() => {
// SSR ortamları için window'un tanımlı olduğundan emin olun
if (typeof window === 'undefined') {
return;
}
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize);
// Temizleme fonksiyonu: olay dinleyicisini kaldır
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Boş bağımlılık dizisi, bu etkinin mount'ta bir kez çalıştığı ve unmount'ta temizlendiği anlamına gelir
return windowSize;
}
// Kullanım:
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
Buradaki boş bağımlılık dizisi [], olay dinleyicisinin component mount olduğunda bir kez eklendiği ve kaldırıldığında bir kez kaldırıldığı anlamına gelir, bu da birden fazla dinleyicinin eklenmesini veya component gittikten sonra kalmasını önler. typeof window !== 'undefined' kontrolü, modern web geliştirmede ilk yükleme sürelerini ve SEO'yu iyileştirmek için yaygın bir uygulama olan Sunucu Tarafı Oluşturma (SSR) ortamlarıyla uyumluluğu sağlar.
Örnek 2: useOnlineStatus – Küresel Ağ Durumunu Yönetme
Ağ bağlantısına dayanan uygulamalar (örneğin, gerçek zamanlı işbirliği araçları, veri senkronizasyon uygulamaları) için kullanıcının çevrimiçi durumunu bilmek esastır. Bu hook, yine uygun temizlikle bunu izlemenin bir yolunu sunar.
Ağ Durumu: {isOnline ? 'Bağlı' : 'Bağlantı Kesildi'}.
Bu, güvenilir olmayan internet bağlantılarına sahip bölgelerdeki kullanıcılara geri bildirim sağlamak için hayati önem taşır.
import React, { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
useEffect(() => {
// SSR ortamları için navigator'un tanımlı olduğundan emin olun
if (typeof navigator === 'undefined') {
return;
}
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
// Temizleme fonksiyonu: olay dinleyicilerini kaldır
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []); // mount'ta bir kez çalışır, unmount'ta temizlenir
return isOnline;
}
// Kullanım:
function NetworkStatusIndicator() {
const isOnline = useOnlineStatus();
return (
useWindowSize'a benzer şekilde, bu hook window nesnesine küresel olay dinleyicileri ekler ve kaldırır. Temizlik olmadan, bu dinleyiciler kalıcı olur, kaldırılmış component'ler için durumu güncellemeye devam eder, bu da bellek sızıntılarına ve konsol uyarılarına yol açar. navigator için yapılan ilk durum kontrolü, SSR uyumluluğunu sağlar.
Örnek 3: useKeyPress – Erişilebilirlik için Gelişmiş Olay Dinleyici Yönetimi
Etkileşimli uygulamalar genellikle klavye girişi gerektirir. Bu hook, dünya çapında erişilebilirlik ve gelişmiş kullanıcı deneyimi için kritik olan belirli tuş basımlarını nasıl dinleyeceğinizi gösterir.
Boşluk tuşuna basın: {isSpacePressed ? 'Basıldı!' : 'Bırakıldı'} Enter tuşuna basın: {isEnterPressed ? 'Basıldı!' : 'Bırakıldı'} Klavye ile gezinme, verimli etkileşim için küresel bir standarttır.
import React, { useState, useEffect } from 'react';
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => {
const downHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
// Temizleme fonksiyonu: her iki olay dinleyicisini de kaldır
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, [targetKey]); // targetKey değişirse yeniden çalıştır
return keyPressed;
}
// Kullanım:
function KeyboardListener() {
const isSpacePressed = useKeyPress(' ');
const isEnterPressed = useKeyPress('Enter');
return (
Buradaki temizleme fonksiyonu, hem keydown hem de keyup dinleyicilerini dikkatlice kaldırarak kalıcı olmalarını önler. Eğer targetKey bağımlılığı değişirse, eski tuş için önceki dinleyiciler kaldırılır ve yeni tuş için yenileri eklenir, böylece yalnızca ilgili dinleyicilerin aktif olması sağlanır.
Örnek 4: useInterval – `useRef` ile Sağlam Bir Zamanlayıcı Yönetimi Hook'u
Daha önce useInterval'ı görmüştük. Şimdi useRef'in, etkilerdeki zamanlayıcılarla ilgili yaygın bir zorluk olan eski kapanışları (stale closures) nasıl önlediğine daha yakından bakalım.
Hassas zamanlayıcılar, oyunlardan endüstriyel kontrol panellerine kadar birçok uygulama için temeldir.
import React, { useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// En son callback'i hatırla. Bu, her zaman güncel 'callback' fonksiyonuna sahip olmamızı sağlar,
// 'callback'in kendisi sık sık değişen component state'ine bağlı olsa bile.
// Bu etki sadece 'callback'in kendisi değişirse yeniden çalışır (örneğin, 'useCallback' nedeniyle).
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// interval'ı kur. Bu etki sadece 'delay' değişirse yeniden çalışır.
useEffect(() => {
function tick() {
// ref'ten en son callback'i kullan
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]); // Sadece delay değişirse interval kurulumunu yeniden çalıştır
}
// Kullanım:
function Stopwatch() {
const [seconds, setSeconds] = React.useState(0);
const [isRunning, setIsRunning] = React.useState(false);
useInterval(
() => {
if (isRunning) {
setSeconds((prevSeconds) => prevSeconds + 1);
}
},
isRunning ? 1000 : null // Çalışmıyorken delay null'dır, bu da interval'ı duraklatır
);
return (
Kronometre: {seconds} saniye
savedCallback için useRef kullanımı çok önemli bir modeldir. Bu olmadan, eğer callback (örneğin, setCount(count + 1) kullanarak bir sayacı artıran bir fonksiyon) doğrudan ikinci useEffect için bağımlılık dizisinde olsaydı, interval her count değiştiğinde temizlenir ve yeniden kurulurdu, bu da güvenilmez bir zamanlayıcıya yol açardı. En son callback'i bir ref'te saklayarak, interval'ın kendisinin yalnızca delay değiştiğinde yeniden kurulması gerekirken, `tick` fonksiyonu her zaman `callback` fonksiyonunun en güncel sürümünü çağırır ve eski kapanışları (stale closures) önler.
Örnek 5: useDebounce – Zamanlayıcılar ve Temizlikle Performansı Optimize Etme
Debouncing, bir fonksiyonun çağrılma sıklığını sınırlamak için yaygın bir tekniktir ve genellikle arama girdileri veya pahalı hesaplamalar için kullanılır. Temizlik burada, birden fazla zamanlayıcının eş zamanlı çalışmasını önlemek için kritik öneme sahiptir.
Mevcut Arama Terimi: {searchTerm} Gecikmeli Arama Terimi (API çağrısı muhtemelen bunu kullanır): {debouncedSearchTerm} Kullanıcı girişini optimize etmek, özellikle çeşitli ağ koşullarında sorunsuz etkileşimler için çok önemlidir.
import React, { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// Gecikmeli değeri güncellemek için bir timeout ayarla
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Temizleme fonksiyonu: timeout tetiklenmeden önce değer veya gecikme değişirse timeout'u temizle
return () => {
clearTimeout(handler);
};
}, [value, delay]); // Sadece değer veya gecikme değişirse etkiyi yeniden çağır
return debouncedValue;
}
// Kullanım:
function SearchBar() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms ile debounce yap
useEffect(() => {
if (debouncedSearchTerm) {
console.log('Aranıyor:', debouncedSearchTerm);
// Gerçek bir uygulamada, burada bir API çağrısı gönderirdiniz
}
}, [debouncedSearchTerm]);
return (
Temizlemedeki clearTimeout(handler), kullanıcının hızlı yazması durumunda önceki, bekleyen zaman aşımlarının iptal edilmesini sağlar. Yalnızca delay süresi içindeki son giriş setDebouncedValue'yu tetikleyecektir. Bu, pahalı işlemlerin (API çağrıları gibi) aşırı yüklenmesini önler ve uygulama yanıt verebilirliğini artırır, bu da küresel olarak kullanıcılar için büyük bir avantajdır.
Gelişmiş Temizleme Modelleri ve Dikkat Edilmesi Gerekenler
Etki temizliğinin temel ilkeleri basit olsa da, gerçek dünya uygulamaları genellikle daha incelikli zorluklar sunar. Gelişmiş modelleri ve dikkat edilmesi gerekenleri anlamak, özel hook'larınızın sağlam ve uyarlanabilir olmasını sağlar.
Bağımlılık Dizisini Anlamak: İki Ucu Keskin Kılıç
Bağımlılık dizisi, etkinizin ne zaman çalışacağının bekçisidir. Bunu yanlış yönetmek iki ana soruna yol açabilir:
- Bağımlılıkları Atlama: Etkinizin içinde kullandığınız bir değeri bağımlılık dizisine eklemeyi unutursanız, etkiniz "eski" bir kapanışla (stale closure) çalışabilir, yani durumun veya prop'ların eski bir sürümüne referans verir. Bu, etkinin (ve temizliğinin) güncel olmayan bilgiler üzerinde çalışabileceği için ince hatalara ve yanlış davranışlara yol açabilir. React ESLint eklentisi bu sorunları yakalamaya yardımcı olur.
- Aşırı Bağımlılık Belirtme: Gereksiz bağımlılıkları, özellikle her render'da yeniden oluşturulan nesneleri veya fonksiyonları dahil etmek, etkinizin çok sık yeniden çalışmasına (ve dolayısıyla yeniden temizlenip yeniden kurulmasına) neden olabilir. Bu, performans düşüşüne, titreyen UI'lara ve verimsiz kaynak yönetimine yol açabilir.
Bağımlılıkları stabilize etmek için, fonksiyonlar için useCallback ve yeniden hesaplanması pahalı olan nesneler veya değerler için useMemo kullanın. Bu hook'lar değerlerini memoize eder (belleğe alır), bağımlılıkları gerçekten değişmediğinde alt component'lerin gereksiz yere yeniden render edilmesini veya etkilerin yeniden yürütülmesini önler.
Sayı: {count} Bu, dikkatli bağımlılık yönetimini gösterir.
import React, { useEffect, useState, useCallback, useMemo } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [filter, setFilter] = useState('');
// useEffect'in gereksiz yere yeniden çalışmasını önlemek için fonksiyonu memoize et
const fetchData = useCallback(async () => {
console.log('Filtre ile veri çekiliyor:', filter);
// Burada bir API çağrısı olduğunu hayal edin
return `Sayı ${count} için ${filter} verisi`;
}, [filter, count]); // fetchData sadece filter veya count değişirse değişir
// Gereksiz yeniden render'ları/etkileri önlemek için bir bağımlılık olarak kullanılıyorsa nesneyi memoize et
const complexOptions = useMemo(() => ({
retryAttempts: 3,
timeout: 5000
}), []); // Boş bağımlılık dizisi, options nesnesinin bir kez oluşturulduğu anlamına gelir
useEffect(() => {
let isActive = true;
fetchData().then(data => {
if (isActive) {
console.log('Alındı:', data);
}
});
return () => {
isActive = false;
console.log('Fetch etkisi için temizleme.');
};
}, [fetchData, complexOptions]); // Şimdi, bu etki sadece fetchData veya complexOptions gerçekten değiştiğinde çalışır
return (
`useRef` ile Eski Kapanışları (Stale Closures) Ele Alma
useRef'in, render'lar arasında kalıcı olan ancak yenilerini tetiklemeyen değiştirilebilir bir değeri nasıl saklayabildiğini gördük. Bu, özellikle temizleme fonksiyonunuzun (veya etkinin kendisinin) bir prop veya durumun *en son* sürümüne erişmesi gerektiğinde, ancak o prop/durumu bağımlılık dizisine dahil etmek istemediğinizde (bu, etkinin çok sık yeniden çalışmasına neden olur) kullanışlıdır.
2 saniye sonra bir mesaj kaydeden bir etki düşünün. Eğer `count` değişirse, temizliğin *en son* count'a ihtiyacı vardır.
Mevcut Sayı: {count} 2 saniye sonra ve temizleme sırasında konsoldaki sayı değerlerini gözlemleyin.
import React, { useEffect, useState, useRef } from 'react';
function DelayedLogger() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
// ref'i en son count değeriyle güncel tut
useEffect(() => {
latestCount.current = count;
}, [count]);
useEffect(() => {
const timeoutId = setTimeout(() => {
// Bu, her zaman timeout ayarlandığında geçerli olan count değerini loglar
console.log(`Etki geri çağrısı: Sayı ${count} idi`);
// Bu, useRef sayesinde her zaman EN SON count değerini loglar
console.log(`ref aracılığıyla etki geri çağrısı: En son sayı ${latestCount.current}`);
}, 2000);
return () => {
clearTimeout(timeoutId);
// Bu temizleme fonksiyonu ayrıca latestCount.current'a da erişebilecektir
console.log(`Temizleme: Temizleme sırasında en son sayı ${latestCount.current} idi`);
};
}, []); // Boş bağımlılık dizisi, etki bir kez çalışır
return (
DelayedLogger ilk render edildiğinde, boş bağımlılık dizisine sahip `useEffect` çalışır. `setTimeout` planlanır. Eğer 2 saniye geçmeden sayacı birkaç kez artırırsanız, `latestCount.current` ilk `useEffect` aracılığıyla (her `count` değişiminden sonra çalışır) güncellenir. `setTimeout` nihayet tetiklendiğinde, kapanışından (closure) gelen `count`'a erişir (bu, etki çalıştığı andaki count'tur), ancak en son durumu yansıtan mevcut ref'ten `latestCount.current`'a erişir. Bu ayrım, sağlam etkiler için çok önemlidir.
Tek Bir Component'te Çoklu Etkiler vs. Özel Hook'lar
Tek bir component içinde birden fazla useEffect çağrısı olması tamamen kabul edilebilir. Aslında, her etki ayrı bir yan etkiyi yönettiğinde bu teşvik edilir. Örneğin, bir useEffect veri çekmeyi, diğeri bir WebSocket bağlantısını yönetmeyi ve üçüncüsü küresel bir olayı dinlemeyi halledebilir.
Ancak, bu ayrı etkiler karmaşık hale geldiğinde veya aynı etki mantığını birden fazla component'te yeniden kullandığınızı fark ettiğinizde, bu mantığı bir özel hook'a soyutlamanız gerektiğinin güçlü bir göstergesidir. Özel hook'lar modülerliği, yeniden kullanılabilirliği ve daha kolay test edilebilirliği teşvik ederek kod tabanınızı büyük projeler ve çeşitli geliştirme ekipleri için daha yönetilebilir ve ölçeklenebilir hale getirir.
Etkilerde Hata Yönetimi
Yan etkiler başarısız olabilir. API çağrıları hata döndürebilir, WebSocket bağlantıları kopabilir veya harici kütüphaneler istisna fırlatabilir. Özel hook'larınız bu senaryoları zarif bir şekilde ele almalıdır.
- Durum Yönetimi: Hata durumunu yansıtmak için yerel durumu (örneğin,
setError(true)) güncelleyin, böylece component'iniz bir hata mesajı veya yedek bir UI oluşturabilir. - Günlük Kaydı (Logging): Farklı ortamlar ve kullanıcı tabanları arasında hata ayıklama için paha biçilmez olan sorunları yakalamak ve bildirmek için
console.error()kullanın veya küresel bir hata günlüğü hizmetiyle entegre olun. - Yeniden Deneme Mekanizmaları: Ağ işlemleri için, geçici ağ sorunlarını ele almak amacıyla hook içinde yeniden deneme mantığı (uygun üstel geri çekilme ile) uygulamayı düşünün, bu da daha az kararlı internet erişimine sahip bölgelerdeki kullanıcılar için dayanıklılığı artırır.
Blog yazısı yükleniyor... (Yeniden deneme: {retries}) Hata: {error.message} {retries < 3 && 'Yakında yeniden denenecek...'} Blog yazısı verisi yok. {post.author} {post.content}
import React, { useState, useEffect } from 'react';
function useReliableDataFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [retries, setRetries] = useState(0);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
let timeoutId;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
if (response.status === 404) {
throw new Error('Kaynak bulunamadı.');
} else if (response.status >= 500) {
throw new Error('Sunucu hatası, lütfen tekrar deneyin.');
} else {
throw new Error(`HTTP hatası! durum: ${response.status}`);
}
}
const result = await response.json();
setData(result);
setRetries(0); // Başarı durumunda yeniden denemeleri sıfırla
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch kasıtlı olarak iptal edildi');
} else {
console.error('Fetch hatası:', err);
setError(err);
// Belirli hatalar veya deneme sayısı için yeniden deneme mantığı uygula
if (retries < 3) { // Maksimum 3 yeniden deneme
timeoutId = setTimeout(() => {
setRetries(prev => prev + 1);
}, Math.pow(2, retries) * 1000); // Üstel geri çekilme (1s, 2s, 4s)
}
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortController.abort();
clearTimeout(timeoutId); // unmount/re-render sırasında yeniden deneme timeout'unu temizle
};
}, [url, retries]); // URL değişikliğinde veya yeniden deneme girişiminde yeniden çalıştır
return { data, loading, error, retries };
}
// Kullanım:
function BlogPost({ postId }) {
const { data: post, loading, error, retries } = useReliableDataFetch(`https://api.example.com/posts/${postId}`);
if (loading) return {post.title}
Bu geliştirilmiş hook, yeniden deneme zaman aşımını temizleyerek agresif bir temizlik gösterir ve ayrıca sağlam hata yönetimi ve basit bir yeniden deneme mekanizması ekler, bu da uygulamayı geçici ağ sorunlarına veya arka uç aksaklıklarına karşı daha dayanıklı hale getirerek küresel olarak kullanıcı deneyimini artırır.
Temizlikle Birlikte Özel Hook'ları Test Etme
Kapsamlı test, herhangi bir yazılım için, özellikle özel hook'lardaki yeniden kullanılabilir mantık için çok önemlidir. Yan etkileri ve temizliği olan hook'ları test ederken şunları sağlamanız gerekir:
- Etkinin bağımlılıklar değiştiğinde doğru çalıştığını.
- Temizleme fonksiyonunun, etki yeniden çalışmadan önce çağrıldığını (eğer bağımlılıklar değişirse).
- Temizleme fonksiyonunun, component (veya hook'un tüketicisi) kaldırıldığında çağrıldığını.
- Kaynakların düzgün bir şekilde serbest bırakıldığını (örneğin, olay dinleyicilerinin kaldırılması, zamanlayıcıların temizlenmesi).
@testing-library/react-hooks (veya component düzeyinde test için @testing-library/react) gibi kütüphaneler, hook'ları izole bir şekilde test etmek için yardımcı programlar sunar, bunlar arasında yeniden render'ları ve kaldırmayı simüle etme yöntemleri bulunur, bu da temizleme fonksiyonlarının beklendiği gibi davrandığını doğrulamanıza olanak tanır.
Özel Hook'larda Etki Temizliği için En İyi Pratikler
Özetlemek gerekirse, React özel hook'larınızda etki temizliğinde ustalaşmak için temel en iyi pratikler şunlardır, bu da uygulamalarınızın tüm kıtalardaki ve cihazlardaki kullanıcılar için sağlam ve performanslı olmasını sağlar:
-
Her Zaman Temizlik Sağlayın: Eğer
useEffect'iniz olay dinleyicileri kaydeder, abonelikler kurar, zamanlayıcılar başlatır veya herhangi bir harici kaynak ayırırsa, bu eylemleri geri almak için mutlaka bir temizleme fonksiyonu döndürmelidir. -
Etkileri Odaklı Tutun: Her
useEffecthook'u ideal olarak tek, bütünsel bir yan etkiyi yönetmelidir. Bu, etkilerin ve temizlik mantıklarının okunmasını, hata ayıklanmasını ve anlaşılmasını kolaylaştırır. -
Bağımlılık Dizisine Dikkat Edin: Bağımlılık dizisini doğru bir şekilde tanımlayın. mount/unmount etkileri için `[]` kullanın ve etkinin dayandığı component kapsamınızdaki tüm değerleri (prop'lar, durum, fonksiyonlar) dahil edin. Fonksiyon ve nesne bağımlılıklarını stabilize etmek ve gereksiz etki yeniden yürütmelerini önlemek için
useCallbackveuseMemo'dan yararlanın. -
Değişebilir Değerler için
useRef'ten Yararlanın: Bir etkinin veya temizleme fonksiyonunun *en son* değiştirilebilir değere (durum veya prop'lar gibi) erişmesi gerektiğinde, ancak bu değerin etkinin yeniden yürütülmesini tetiklemesini istemediğinizde, onu biruseRef'te saklayın. Ref'i, o değeri bağımlılık olarak alan ayrı biruseEffectiçinde güncelleyin. - Karmaşık Mantığı Soyutlayın: Bir etki (veya ilgili bir grup etki) karmaşık hale gelirse veya birden çok yerde kullanılırsa, onu bir özel hook'a çıkarın. Bu, kod organizasyonunu, yeniden kullanılabilirliği ve test edilebilirliği artırır.
- Temizliğinizi Test Edin: Özel hook'larınızın temizlik mantığının testini geliştirme iş akışınıza entegre edin. Bir component kaldırıldığında veya bağımlılıklar değiştiğinde kaynakların doğru bir şekilde serbest bırakıldığından emin olun.
-
Sunucu Tarafı Oluşturmayı (SSR) Dikkate Alın:
useEffectve temizleme fonksiyonlarının SSR sırasında sunucuda çalışmadığını unutmayın. Kodunuzun, ilk sunucu render'ı sırasında tarayıcıya özgü API'lerin (windowveyadocumentgibi) yokluğunu zarif bir şekilde ele aldığından emin olun. - Sağlam Hata Yönetimi Uygulayın: Etkilerinizdeki potansiyel hataları öngörün ve ele alın. Hataları UI'ya iletmek için durumu ve teşhis için günlük hizmetlerini kullanın. Ağ işlemleri için, dayanıklılık için yeniden deneme mekanizmalarını düşünün.
Sonuç: React Uygulamalarınızı Sorumlu Yaşam Döngüsü Yönetimi ile Güçlendirme
React özel hook'ları, özenli etki temizliği ile birleştiğinde, yüksek kaliteli web uygulamaları oluşturmak için vazgeçilmez araçlardır. Yaşam döngüsü yönetimi sanatında ustalaşarak, bellek sızıntılarını önler, beklenmedik davranışları ortadan kaldırır, performansı optimize eder ve konumları, cihazları veya ağ koşulları ne olursa olsun kullanıcılarınız için daha güvenilir ve tutarlı bir deneyim yaratırsınız.
useEffect'in gücüyle gelen sorumluluğu benimseyin. Özel hook'larınızı temizliği göz önünde bulundurarak düşünceli bir şekilde tasarlayarak, sadece işlevsel kod yazmıyorsunuz; aynı zamanda zamanın ve ölçeğin testine dayanan, çeşitli ve küresel bir kitleye hizmet etmeye hazır, dayanıklı, verimli ve sürdürülebilir bir yazılım üretiyorsunuz. Bu ilkelere olan bağlılığınız şüphesiz daha sağlıklı bir kod tabanına ve daha mutlu kullanıcılara yol açacaktır.